import {DisplayEvent, IEvent, ObjectInformation} from "../event-types";
import {Differential, DisplayDifferential} from "./differential";
import {contains} from "../helper";
export class DiffCalculation {
    private readonly diffMargin = 1;
    private readonly scrollMargin = 785 - 689 + 10;

    public calculateDiffBetweenDisplayEvents(events: IEvent[]) {
        const displayEvents = <DisplayEvent[]> events.filter(e => e.eventType === 'display-event');
        // Remove all display events that are not in the main view
        const mainViewDisplayEvents = displayEvents.filter(d => d.elements.find(e => e.label === 'MainTabControl'))
            .sort((a, b) => a.timestamp - b.timestamp);
        const diffs: DisplayDifferential[] = [];
        for (let i = 0; i < mainViewDisplayEvents.length - 2; i++) {
            const current = mainViewDisplayEvents[i];
            const next = mainViewDisplayEvents[i + 1];
            const currentDisplayDifferential: DisplayDifferential = {
                currentTimestamp: current.timestamp,
                nextTimestamp: next.timestamp,
                diffs: []
            };
            const isMeasureScene = current.zones.some(z => z.label === 'Measures');
            const isMeasureSceneNext = next.zones.some(z => z.label === 'Measures');
            // We only care about diffs in the same scene and there are only two scenes
            if (isMeasureScene !== isMeasureSceneNext) {
                continue;
            }

            const currentElements = current.elements;
            const nextElements = next.elements;

            for (let element of currentElements) {
                const updatedDiff = this.updatedDiff(element, current, next);
                if (updatedDiff) {
                    currentDisplayDifferential.diffs.push(updatedDiff);
                    continue;
                }
                const deleteDiff = this.deleteDiff(element, current, next);
                if (deleteDiff) {
                    currentDisplayDifferential.diffs.push(deleteDiff);
                }
            }

            for (let element of nextElements) {
                const addDiff = this.addDiff(element, current, next);
                if (addDiff) {
                    currentDisplayDifferential.diffs.push(addDiff);
                }
            }

            diffs.push(currentDisplayDifferential);
        }

        return diffs;
    }

    private updatedDiff(current: ObjectInformation, currentDisplay: DisplayEvent, nextDisplay: DisplayEvent): Differential | undefined {
        const containingZone = currentDisplay.zones.find(z => contains(current, z));
        const nextZone = nextDisplay.zones.find(z => containingZone?.label === z.label);
        const next = nextDisplay.elements.find(e => e.label === current.label && (
            (nextZone && contains(e, nextZone))
            ||
            (Math.abs(e.x - current.x) < this.diffMargin &&
            Math.abs(e.y - current.y) < this.scrollMargin &&
            Math.abs(e.width - current.width) < this.diffMargin &&
            Math.abs(e.height - current.height) < this.diffMargin)));
        if (next && JSON.stringify(current) !== JSON.stringify(next)) {
            return {
                type: 'update',
                current,
                next,
            };
        }
        return undefined;
    }

    private addDiff(next: ObjectInformation, currentDisplay: DisplayEvent, nextDisplay: DisplayEvent): Differential | undefined {
        const containingZone = nextDisplay.zones.find(z => contains(next, z));
        const currentZone = nextDisplay.zones.find(z => containingZone?.label === z.label);
        const current = currentDisplay.elements.find(e => e.label === next.label && (
            (currentZone && contains(e, currentZone))
            ||
            (Math.abs(e.x - next.x) < this.diffMargin &&
                Math.abs(e.y - next.y) < this.scrollMargin &&
                Math.abs(e.width - next.width) < this.diffMargin &&
                Math.abs(e.height - next.height) < this.diffMargin)));
        if(!current) {
            return {
                type: 'add',
                next,
            };
        }
        return undefined;
    }

    private deleteDiff(current: ObjectInformation, _: DisplayEvent, nextDisplay: DisplayEvent): Differential | undefined {
        const next = nextDisplay.elements.find(e => e.label === current.label &&
            Math.abs(e.x - current.x) < this.diffMargin &&
            Math.abs(e.y - current.y) < this.scrollMargin &&
            Math.abs(e.width - current.width) < this.diffMargin &&
            Math.abs(e.height - current.height) < this.diffMargin);
        if (!next) {
            return {
                type: 'delete',
                current,
            };
        }
        return undefined;
    }


}